package figure

import (
	"fmt"
	"math"

	"github.com/fogleman/gg"
)

type Figure struct {
	dc            *gg.Context
	figureSize    int
	fineLineWidth float64
	boldLineWidth float64
	cellSize      float64
	margin        float64
	fontSize      float64
	starPoints    [11]Point
}

func NewFigure(figureSize int) *Figure {
	f := &Figure{}
	if figureSize < 200 {
		figureSize = 200
	}
	if figureSize > 1000 {
		figureSize = 1000
	}
	f.figureSize = figureSize / 100 * 100
	f.cellSize = float64(f.figureSize) * 0.1
	f.margin = f.cellSize / 2
	f.fontSize = f.cellSize * 0.75
	f.boldLineWidth = math.Round(f.cellSize * 0.06)
	if f.boldLineWidth < 2 {
		f.boldLineWidth = 2
	}
	f.fineLineWidth = math.Floor(f.boldLineWidth/3 + 0.4)
	if f.fineLineWidth < 1 {
		f.fineLineWidth = 1
	}

	f.starPoints = starPoints(f.fontSize / 2)

	f.dc = gg.NewContext(figureSize, figureSize)
	f.dc.SetRGB(1, 1, 1)
	f.dc.Clear()

	f.dc.SetLineCapSquare()

	return f
}

func (f *Figure) DrawFrame() {
	dc := f.dc
	var x1, x2, y1, y2 float64
	dc.SetRGB(0.5, 0.5, 0.5)
	dc.SetLineWidth(f.fineLineWidth)
	y1, y2 = f.margin, float64(f.figureSize)-f.margin
	for i := f.margin + f.cellSize; i < float64(f.figureSize)-f.margin; i += f.cellSize {
		x1, x2 = i, i
		dc.DrawLine(x1, y1, x2, y2)
		dc.DrawLine(y1, x1, y2, x2)
	}
	dc.Stroke()

	dc.SetRGB(0, 0, 0)
	dc.SetLineWidth(f.boldLineWidth)
	y1, y2 = f.margin, float64(f.figureSize)-f.margin
	for i := f.margin; i < float64(f.figureSize); i += f.cellSize * 3 {
		x1, x2 = i, i
		dc.DrawLine(x1, y1, x2, y2)
		dc.DrawLine(y1, x1, y2, x2)
	}
	dc.Stroke()

	f.DrawUnit()
}

func (f *Figure) Save(fileName string) {
	f.dc.SavePNG(fileName)
}

func (f *Figure) DrawValue(rowNum, colNum, value int) {
	f.dc.SetRGB(0, 0, 0)
	face, _ := gg.LoadFontFace("/Users/zoushengyuan/prog/gitee/dunszou/gosudoku/HONORSans.ttf", f.fontSize)
	f.dc.SetFontFace(face)
	y := f.margin + f.cellSize*float64(rowNum) + f.cellSize*0.5 - f.cellSize*0.08
	x := f.margin + f.cellSize*float64(colNum) + 0.5*f.cellSize
	f.dc.DrawStringAnchored(fmt.Sprint(value), x, y, 0.5, 0.5)
}

func (f *Figure) DrawGiven(rowNum, colNum, value int) {
	f.dc.SetRGB255(44, 95, 244)
	face, _ := gg.LoadFontFace("/Users/zoushengyuan/prog/gitee/dunszou/gosudoku/HONORSans.ttf", f.fontSize)
	f.dc.SetFontFace(face)
	y := f.margin + f.cellSize*float64(rowNum) + f.cellSize*0.5 - f.cellSize*0.08
	x := f.margin + f.cellSize*float64(colNum) + 0.5*f.cellSize
	f.dc.DrawStringAnchored(fmt.Sprint(value), x, y, 0.5, 0.5)
}

func (f *Figure) DrawCands(rowNum, colNum int, cands []int) {
	f.dc.SetRGB(0, 0, 0)
	face, _ := gg.LoadFontFace("/Users/zoushengyuan/prog/gitee/dunszou/gosudoku/HONORSans.ttf", f.fontSize/3)
	f.dc.SetFontFace(face)

	y := f.margin + f.cellSize*float64(rowNum)
	x := f.margin + f.cellSize*float64(colNum)

	for _, cand := range cands {
		dx := x + float64(cand%3)*f.cellSize/3 + f.cellSize/3*0.5
		dy := y + float64(cand/3)*f.cellSize/3 + f.cellSize/3*0.5 - f.cellSize*0.04
		f.dc.DrawStringAnchored(fmt.Sprint(cand+1), dx, dy, 0.5, 0.5)
	}
}

func (f *Figure) DrawSquare(rowNum, colNum int, color string) {
	y := f.margin + f.cellSize*float64(rowNum)
	x := f.margin + f.cellSize*float64(colNum)
	w := f.cellSize
	h := w
	f.dc.DrawRectangle(x, y, w, h)
	switch color {
	case "red":
		f.dc.SetRGB255(249, 220, 217)
	case "blue":
		f.dc.SetRGB255(214, 226, 252)
	case "purple":
		f.dc.SetRGB255(233, 203, 251)
	case "yellow":
		f.dc.SetRGB255(254, 252, 224)
	case "green":
		f.dc.SetRGB255(226, 238, 214)
	default:
		f.dc.SetRGB255(255, 255, 255)
	}
	f.dc.Fill()
}

func (f *Figure) DrawSquareA(rowNum, colNum int, color string) {
	y := f.margin + f.cellSize*float64(rowNum)
	x := f.margin + f.cellSize*float64(colNum)
	w := f.cellSize
	h := w
	f.dc.DrawRectangle(x, y, w, h)
	switch color {
	case "red":
		f.dc.SetRGBA255(249, 220, 217, 128)
	case "blue":
		f.dc.SetRGBA255(214, 226, 252, 128)
	case "purple":
		f.dc.SetRGBA255(233, 203, 251, 128)
	case "yellow":
		f.dc.SetRGBA255(254, 252, 224, 128)
	case "green":
		f.dc.SetRGBA255(226, 238, 214, 128)
	default:
		f.dc.SetRGBA255(255, 255, 255, 128)
	}
	f.dc.Fill()
}

func (f *Figure) DrawMarks(rowNum, colNum int, cands []int, color string) {
	y := f.margin + f.cellSize*float64(rowNum)
	x := f.margin + f.cellSize*float64(colNum)

	switch color {
	case "red":
		f.dc.SetRGB255(232, 109, 70)
	case "blue":
		f.dc.SetRGB255(109, 196, 227)
	case "purple":
		f.dc.SetRGB255(175, 127, 225)
	case "yellow":
		f.dc.SetRGB255(239, 193, 84)
	case "green":
		f.dc.SetRGB255(152, 208, 119)
	default:
		f.dc.SetRGB255(255, 255, 255)
	}

	r := f.cellSize / 3 * 0.38
	for _, cand := range cands {
		dx := x + float64(cand%3)*f.cellSize/3 + f.cellSize/3*0.5
		dy := y + float64(cand/3)*f.cellSize/3 + f.cellSize/3*0.5 - f.cellSize*0.01
		f.dc.DrawCircle(dx, dy, r)
		f.dc.Fill()
	}
}

func (f *Figure) DrawLineA(row1, col1, cand1, row2, col2, cand2 int, color string) {
	f.dc.SetLineWidth(f.boldLineWidth)
	setLineColor(f, color)

	x1, y1, x2, y2 := calcLineXY(f, row1, col1, cand1, row2, col2, cand2)
	f.dc.DrawLine(x1, y1, x2, y2)
	f.dc.Stroke()
}

func (f *Figure) DrawDashLineA(row1, col1, cand1, row2, col2, cand2 int, color string) {
	f.dc.SetDash(f.cellSize / 9)
	f.DrawLineA(row1, col1, cand1, row2, col2, cand2, color)
	f.dc.SetDash()
}

func (f *Figure) DrawArrowA(row1, col1, cand1, row2, col2, cand2 int, color string) {
	f.dc.SetLineWidth(f.boldLineWidth)
	setLineColor(f, color)

	x1, y1, x2, y2 := calcLineXY(f, row1, col1, cand1, row2, col2, cand2)
	f.dc.DrawLine(x1, y1, x2, y2)
	f.dc.Stroke()
	f.drawArrow(x1, y1, x2, y2)
}

func (f *Figure) DrawDashArrowA(row1, col1, cand1, row2, col2, cand2 int, color string) {
	f.dc.SetLineWidth(f.boldLineWidth)
	setLineColor(f, color)
	f.dc.SetDash(f.cellSize / 9)

	x1, y1, x2, y2 := calcLineXY(f, row1, col1, cand1, row2, col2, cand2)
	f.dc.DrawLine(x1, y1, x2, y2)
	f.dc.Stroke()
	f.dc.SetDash()
	f.drawArrow(x1, y1, x2, y2)
}

func setLineColor(f *Figure, color string) {
	switch color {
	case "red":
		f.dc.SetRGBA255(208, 58, 32, 192)
	case "blue":
		f.dc.SetRGBA255(0, 86, 214, 192)
	case "purple":
		f.dc.SetRGBA255(122, 33, 158, 192)
	case "yellow":
		f.dc.SetRGBA255(211, 131, 1, 192)
	case "green":
		f.dc.SetRGBA255(102, 157, 52, 192)
	default:
		f.dc.SetRGBA255(255, 255, 255, 192)
	}
}

func calcLineXY(f *Figure, row1, col1, cand1, row2, col2, cand2 int) (
	float64, float64, float64, float64) {
	x1 := f.margin + f.cellSize*float64(col1) +
		float64(cand1%3)*f.cellSize/3 + f.cellSize/3*0.5
	y1 := f.margin + f.cellSize*float64(row1) +
		float64(cand1/3)*f.cellSize/3 + f.cellSize/3*0.5

	x2 := f.margin + f.cellSize*float64(col2) +
		float64(cand2%3)*f.cellSize/3 + f.cellSize/3*0.5
	y2 := f.margin + f.cellSize*float64(row2) +
		float64(cand2/3)*f.cellSize/3 + f.cellSize/3*0.5
	return x1, y1, x2, y2
}

func (f *Figure) drawArrow(x1, y1, x2, y2 float64) {
	dy := y2 - y1
	dx := x2 - x1
	angle := math.Atan2(dy, dx)
	angle += math.Pi / 6
	len := f.cellSize * 0.15
	x3 := x2 - len*math.Cos(angle)
	y3 := y2 - len*math.Sin(angle)
	f.dc.MoveTo(x2, y2)
	f.dc.LineTo(x3, y3)

	angle -= math.Pi / 3
	x3 = x2 - len*math.Cos(angle)
	y3 = y2 - len*math.Sin(angle)
	f.dc.MoveTo(x2, y2)
	f.dc.LineTo(x3, y3)

	f.dc.SetDash()
	f.dc.Stroke()

}

func (f *Figure) DrawUnit() {
	f.dc.SetRGB(0.5, 0.5, 0.5)
	face, _ := gg.LoadFontFace("/Users/zoushengyuan/prog/gitee/dunszou/gosudoku/HONORSans.ttf", f.fontSize/2.3) //./HONORSans.ttf
	f.dc.SetFontFace(face)

	pos := f.margin + f.cellSize/2
	for i := 0; i < 9; i++ {
		f.dc.DrawStringAnchored(fmt.Sprint(i), pos, f.margin/2.3, 0.5, 0.5)
		pos += f.cellSize
	}

	pos = f.margin + f.cellSize/2
	for i := 0; i < 9; i++ {
		f.dc.DrawStringAnchored(fmt.Sprint(i), f.margin/2.3, pos, 0.5, 0.5)
		pos += f.cellSize
	}

}

func (f *Figure) DrawStar(rowNum int, colNum int) {
	f.dc.SetLineWidth(f.fineLineWidth)
	f.dc.SetRGB(255, 0, 0)

	x := f.margin + f.cellSize*float64(colNum) + 0.5*f.cellSize
	y := f.margin + f.cellSize*float64(rowNum) + 0.5*f.cellSize

	ps := f.starPoints
	f.dc.NewSubPath()
	f.dc.MoveTo(ps[0].x+x, ps[0].y+y)
	for i := 1; i < 11; i++ {
		f.dc.LineTo(ps[i].x+x, ps[i].y+y)
	}
	f.dc.ClosePath()
	f.dc.SetRGB255(255, 0, 0)
	f.dc.Fill()

	f.dc.Stroke()
}

func (f *Figure) DrawLine(r1, c1, r2, c2 float64) {
	f.dc.SetLineWidth(f.fineLineWidth)
	f.dc.SetRGB(0, 0, 0)

	x1 := f.margin + f.cellSize*float64(c1) + 0.5*f.cellSize
	x2 := f.margin + f.cellSize*float64(c2) + 0.5*f.cellSize
	y1 := f.margin + f.cellSize*float64(r1) + 0.5*f.cellSize
	y2 := f.margin + f.cellSize*float64(r2) + 0.5*f.cellSize
	f.dc.DrawLine(x1, y1, x2, y2)

	f.dc.Stroke()
}

func (f *Figure) DrawCandidates(gs *GridSetting) {
	f.dc.SetRGB(0, 0, 0)
	face, _ := gg.LoadFontFace("/Users/zoushengyuan/prog/gitee/dunszou/gosudoku/HONORSans.ttf", f.fontSize/3)
	f.dc.SetFontFace(face)

	for rowNum := 0; rowNum < 9; rowNum++ {
		for colNum := 0; colNum < 9; colNum++ {
			if gs.cells[rowNum][colNum] != 0 {
				continue
			}
			cx := f.margin + f.cellSize*float64(colNum)
			cy := f.margin + f.cellSize*float64(rowNum)

			n := 0
			for i := 0; i < 3; i++ {
				for j := 0; j < 3; j++ {
					if !gs.candidates[rowNum][colNum][n] {
						n++
						continue
					}
					x := cx + f.cellSize/3*float64(j) + f.cellSize/3*0.5
					y := cy + f.cellSize/3*float64(i) + f.cellSize/3*0.5
					f.dc.DrawStringAnchored(fmt.Sprint(n+1), x, y, 0.5, 0.5)
					n++
				}
			}

		}
	}
}
